问题实例
</br>
假设有一个Widget类:
1 | class Widget { |
我们试图建立起int、widget之间的映射关联,那么使用使用map是再恰当不过的:
1 | map<int, Widget> m; |
然而这种写法对性能的冲击极大。
map::operator[]与map::insert
map::operator[]的具体实现
map的operator[]
与别的容器的operator[]
颇有不同,它所执行的功能是“更新或添加”,具体来说,
1 | m[k] = v; |
上述表达式所执行的操作依次为:
- 检查键k是否在map中,如果没有,则insert
pair<const k,v>
- 有,则更新k所对应的value
operator[]
默认返回一个value的引用,这在更新操作中再正常不过,但对于insert操作,它会使用value的默认构造函数构造一个,然后返回这个新建立对象的引用。具体来说,
1 | m[1] = 1.50;//m中不存在k==1 |
等价于
1 | typedef map<int, Widget> IntWidgetMap; |
用insert直接取代operator[]中的插入操作
显然,上述代码在map中插入了一个pair,其first是我们指定的key,second则是默认初始化的widget,最后再执行了赋值操作。
以value初始化widget自然会比上述代码更加高效,所以,我们应该直接使用insert代替operator[]
:
1 | m.insert(IntWidgetMap::value_type(1, 1.50)); |
这种写法一共节约了3次函数调用:
- 一次建立widget临时对象
- 一次销毁widget临时对象
- 一次widget赋值操作
两全其美的方法
</br>
出于对更高效的渴望,我们能否扬长避短,实现如下的功能?
1 | //如果k在m中,调用operator[],否则执行insert |
实现该函数实际上并不困难:
1 | template<typename MapType,typename KeyArgType, typename ValueArgtype> |
在上述实例中,keytype与valuetype不必是存储在map里的类型,它们只需要能够完成隐式转换即可。
总结
</br>
本节的重点并不在于如何去实现AddOrUpdate这样的函数,而在于强调operator[]
与insert在不同情况下效率的不同,如果我们能明确程序行为,那我们应当谨慎地选用它们中的一个。